4. HTMLを解析する──HTMLからDOMツリーへの変換
HTML をブラウザに実装するとは、HTML で書かれた文字列を解釈し、解釈した文字列を画面に描画するということ 文字列を解釈するとは、文字列を小さな意味のある単位に分割(字句解析)し、その分解された要素の構造や階層関係を解析(構文解析)すること HTML の字句解析
code:mermaid
https://scrapbox.io/files/6739e231da86f4525b6c3640.png
HTML の文字列を 1 文字ずつ処理していく、現在の状態と次に処理する 1 文字によって 状態 の更新と トークン(HTML トークン)の生成が行われる トークンは 6 種類存在するが、本書では 4 種類のみ扱う
開始タグ
終了タグ
文字
状態は 80 種類存在するが、本書では 17 種類のみ扱う
初期状態
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が < であれば、状態を Tag open に変更する
EOF に到達した場合、 EOF トークンを返し、それ以外は文字トークンを返す
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が / の場合、状態を End tag open に変更する
文字がアルファベットの場合、以下を行う
文字を再度取り扱うためのフラグ(reconsume)を true に
状態を TagName に変更する
開始タグトークンを生成する
EOF に到達した場合、 EOF トークンを返す
それ以外は reconsume を true にし、状態を Data に戻す
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字がアルファベットの場合、以下を行う
reconsume を true にする
状態を Tag name に変更する
終了タグトークンを生成する
EOF に到達した場合、 EOF トークンを返す
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が ホワイトスペース の場合、状態を Before attribute name に変更する 文字が / の場合、状態を Self-closing start tag に変更する
文字が > の場合、以下を行う
状態を Data に戻す
現在(一番最後に生成した)の開始タグ or 終了タグのトークンを返す
文字がアルファベットの場合、文字を現在のタグ(トークン)の名前として追加する
EOF に到達した場合、 EOF トークンを返す
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が / か >、または EOF に到達した場合、以下を行う
reconsume を true にする
状態を After attribute name に変更する
上記以外の場合、以下を行う
reconsume を true にする
状態を Attribute name に変更する
現在の開始タグトークンに属性を追加する
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字がホワイトスペースか、/、 >、または EOF に到達した場合、以下を行う
reconsume を true にする
状態を After attribute name に変更する
文字が = の場合、以下を行う
状態を Before attribute value 状態に変更する
文字を現在の属性の名前として追加する
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字がホワイトスペースの場合、無視する
文字が / の場合、状態を Self-closing start tag に変更する
文字が = の場合、状態を Before attribute value に変更する
文字が > の場合、以下を行う
状態を Data に変更する
現在の開始タグトークンを返す
EOF に到達した場合、 EOF トークンを返す
上記以外の場合、以下を行う(Before attribute name と同様)
reconsume を true にする
状態を Attribute name に変更する
現在の開始タグトークンに属性を追加する
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字がホワイトスペースの場合、無視する
文字が " の場合、状態を Attribute value double-quoted に変更する
文字が ' の場合、状態を Attribute value single-quoted に変更する
上記以外の場合、以下を行う
reconsume を true にする
状態を Attribute value unquoted にする
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が " の場合、状態を After attribute value quoted に変更する
EOF に到達した場合、 EOF トークンを返す
上記以外の場合、文字を現在の属性の値として追加する
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が ' の場合、状態を After attribute value quoted に変更する
EOF に到達した場合、 EOF トークンを返す
上記以外の場合、文字を現在の属性の値として追加する
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字がホワイトスペースの場合、状態を Before attribute name に変更する
文字が > の場合、以下を行う
状態を Data に変更する
現在の開始タグトークンを返す
EOF に到達した場合、 EOF トークンを返す
上記以外の場合、文字を現在の属性の値として追加する
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字がホワイトスペースの場合、状態を Before attribute name に変更する
文字が / の場合、状態を Self-closing start tag に変更する
文字が > の場合、以下を行う
状態を Data に変更する
現在の開始タグトークンを返す
EOF に到達した場合、 EOF トークンを返す
上記以外の場合、以下を行う
reconsume を true にする
状態を Before attribute value に変更する
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が > の場合、以下を行う
状態を Data に変更する
現在の開始タグトークンを、自己終了タグであることを表すフラグをオンにして 返す
EOF に到達した場合、 EOF トークンを返す
以下は定義しているが、上記の状態から遷移することが無いので遷移することはないのでは…?? radish-miyazaki.icon 1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が < の場合、状態を Script data less-than sign に変更する
EOF に到達した場合、 EOF トークンを返す
上記以外の場合、文字トークンを返す
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が / の場合、以下を行う
Temporary buffer をリセットする
状態を Script data end tag open に変更する
上記以外の場合、以下を行う
reconsume を true にする
状態を Script data に変更する
文字トークン(>)を返す
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字がアルファベットの場合、以下を行う
reconsume を true にする
状態を Script data end tag name に変更する
終了タグトークンを作成する
上記以外の場合、以下を行う
reconsume を true にする
状態を Script data に変更する
文字トークン(< と /)を返す
1 つの文字を消費し、その文字の種類によって次の行動を決定する
文字が > の場合、以下を行う
状態を Data に変更する
終了タグ(</script>)トークンを返す
文字がアルファベットの場合、以下を行う
reconsume を true にする
文字を Temporary buffer と現在のタグ(トークン)の名前として追加する
上記以外の場合、Temporary buffer に </ と文字を追加した後、バッファ内の文字をバッファに入った順に文字トークンとして返す
その後、reconsume を true にし、状態を Script data に変更する
When a state says to reconsume a matched character in a specified state, that means to switch to that state, but when it attempts to consume the next input character, provide it with the current input character instead.
Tokenization の仕様書で繰り返し出てくるワード①
状態だけを更新し、使用した文字をもう一度解析することを指す
そのため、実装時にはフラグなどで値を保持しておく必要がある
Certain states also use a temporary buffer to track progress, and the character reference state uses a return state to return to the state it was invoked from.
Tokenization の仕様書で繰り返し出てくるワード②
実装を簡単にするために、一時的なバッファを保持する状態
仕様書に状態としては 分類されていない
HTML の構文解析
字句解析されたトークンを利用して、構文解析により DOM ツリー を構築する DOM ツリーを構築するノードは、それぞれの HTML タグを表す
Node インタフェースでは、要素を追加する appendChild や insertBefore などのメソッドが定義されている
ノードの種類(Node インタフェースを 継承 したインタフェース) 既存の要素を取得する getElementById や getElementsByClassName などを持つ
e.g. <p> など
インタフェース内で定義されている tagName や id、className を使用して、要素のタグ名や ID、クラス名を取得したり、getAttribute メソッドで要素の属性を取得することが可能
DOM ツリーのルートを持つ
1 つの Web ページに対して 1 つの インスタンス が存在する ただし、iframe は独自の Window オブジェクトを持つ
https://scrapbox.io/files/673ddc1f6c8b2a35b0f13ab4.png
循環参照とは?
オブジェクト同士が互いに参照し合う状態
発生するとどうなる?
循環参照が発生すると、メモリ 管理やオブジェクトの解放が困難になる場合がある 通常、オブジェクトは参照されなくなった時点で GC などにより自動的に解放される しかし、循環参照の場合は、どのオブジェクトも常に参照され続けるため、解放されずにメモリを専有し続ける
回避策
これにより、循環参照が発生しても参照の解除が正しく行われ、メモリリーク を防げる 具体的には、ウィークポインタはストロングポインタの 参照カウンタ が 0 になったときに削除される Rust ではどう回避するか
強い参照は Rc、弱い参照は Weak を使用して使い分けることが可能 Weak は Rc::downgrade を用いて生成するのが一般的
code:rs
pub struct Node {
pub kind: NodeKind,
window: Weak<RefCell<Window>>,
parent: Weak<RefCell<Node>>,
first_child: Option<Rc<RefCell<Node>>>,
last_child: Weak<RefCell<Node>>,
previous_sibling: Weak<RefCell<Node>>,
next_sibling: Option<Rc<RefCell<Node>>>,
}
子ノードや兄弟ノードへの参照は、データ構造の 所有権 に近い役割を持つため Rc を用いている それ以外のノードでは循環参照を避けるため、Weak を用いる
https://scrapbox.io/files/673dd586b6b8c70e152c6cd7.png